﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Glue.Internals.Nulls;

namespace Glue.Internals
{
    internal class CompiledExpression
    {
        private readonly Expression expression;
        public String CallPath { get; private set; }
        public String BasePropertyName { get; private set; }
        private readonly Stack<GenericExpression> getNearestParentCallStack = new Stack<GenericExpression>();        

        internal CompiledExpression(Expression expression)
        {
            this.expression = expression;
            BuildCallPathAndBasePropertyName();
        }

        public Maybe GetNearestParent(object toInvokeOn)
        {
            var maybeSomethingToInvokeOn = new Maybe(toInvokeOn);
            var enu = getNearestParentCallStack.GetEnumerator();
            while (enu.MoveNext())
            {
                if (!maybeSomethingToInvokeOn.HasValue()) return Maybe.Nothing();
                maybeSomethingToInvokeOn =
                    enu.Current.Accessor.InvokeGet(maybeSomethingToInvokeOn.GetValue());
            }
            return maybeSomethingToInvokeOn;
        }

        public IEnumerable<IObjectMemberAccessor> GetEnumeratorForMemberAccessors()
        {
            foreach (var exp in getNearestParentCallStack)
            {
                yield return exp.Accessor;
            }
        }


        private void BuildCallPathAndBasePropertyName()
        {
            var memberExpression = new GenericExpression(expression);
            var memberName = memberExpression.Name;
            getNearestParentCallStack.Push(memberExpression);

            var subExpression = GetSubExpression(memberExpression);
            while (subExpression.NodeType != ExpressionType.Parameter)
            {
                var tempExpression = new GenericExpression(subExpression);
                memberName = tempExpression.Name + "." + memberName;
                getNearestParentCallStack.Push(tempExpression);
                memberExpression = tempExpression;
                subExpression = GetSubExpression(memberExpression);
            }
            CallPath = subExpression.Type.Name + "." + memberName;
            BasePropertyName = memberExpression.Name;
        }

        private Expression GetSubExpression(GenericExpression memberExpression)
        {
            var subExpression = memberExpression.SubExpression;
            if (subExpression.NodeType == ExpressionType.Convert)
            {
                subExpression = ((UnaryExpression)subExpression).Operand;
            }
            return subExpression;
        }
    }
}
